import pandas as pd
from auxiliary import *
import numpy as np
import matplotlib.pyplot as plt
import random


class Data:

    def __init__(self) -> None:
        self.product_info = pd.read_csv('企业数字化-数字化车间智能排产调度挑战赛公开数据\产品信息.csv')
        '''产品信息表'''
        self.route_info = pd.read_csv(
            '企业数字化-数字化车间智能排产调度挑战赛公开数据\工艺路线.csv', encoding='gbk')
        '''工艺路线表'''
        self.equipment_info = pd.read_csv(
            '企业数字化-数字化车间智能排产调度挑战赛公开数据\设备信息.csv', encoding='gbk')
        '''设备信息表'''
        self.product_total_num = self.product_info.shape[0]
        '''产品数量'''
        self.product_id = self.product_info['product_id'].values
        '''产品名列表'''
        self.product_operations = self.__initial_operations()
        '''产品的工序数矩阵,产品号-该产品的工序数,int64[:]'''
        self.max_operation = self.product_operations.max()
        '''产品最大工序数'''
        self.product_joints = self.product_info['product_num'].values
        '''产品节数'''
        self.machine_total_num = self.equipment_info.shape[0]
        '''机器数量'''
        self.machine_id = self.equipment_info['equ_name'].values
        '''机器名列表'''
        self.product_operation_detail = self.__initial_product_operation_detail()
        '''加工详情矩阵,产品号-工序号-机器号-该工序下机器加工时间,int64[:,:,:],注 工序D为8小时,没有节的信息,工序C为节数*0.2小时'''
        self.candidate_machine_index = self.__initial_canditate_machine_index()
        '''候选机器索引矩阵,产品号-工序号-该工序候选机器数量,int64[:,:]'''
        self.candidate_machine = self.__initial_candidate_machine()
        '''候选机器矩阵,产品号-工序号-候选机器矩阵[:candidate_machien_index[product_num][operation_num]]'''
        self.product_route = self.__initial_product_route()
        '''产品工序类型,产品号-工序号-该工序的类型,int64[:,:]'''
        self.C_time = self.__initial_C_time()

        self.size = None
        '''种群规模'''
        self.global_size = None
        '''全局初始化的规模'''
        self.local_size = None
        '''局部初始化的规模'''
        self.random_size = None
        '''随机初始化的规模'''
        self.length = self.product_operations.sum()
        '''基因码的长度'''
        self.route_type = self.__initial_route_type()
        '''机器码对应的工序类型'''
        self.OS = None
        '''工序基因码'''
        self.MS = None
        '''机器基因码'''
        self.best_MS = None
        '''最优机器码'''
        self.best_OS = None
        '''最优工序码'''
        self.best_score = float('inf')
        '''最佳得分'''
        self.best_step = float('inf')

    def __initial_canditate_machine_index(self):
        '''初始化候选机器索引矩阵,产品号-工序号-该工序候选机器数量,int64[:,:]'''
        candidate_machine_index = np.zeros(
            shape=(self.product_total_num, self.product_operations.max()), dtype=np.int64)
        for product_num in range(self.product_total_num):
            for operation_num in range(self.product_operations[product_num]):
                candidate_machine_o = np.where(
                    self.product_operation_detail[product_num][operation_num] != 0)[0]
                candidate_machine_index[product_num][operation_num] = candidate_machine_o.shape[0]
        return candidate_machine_index

    def __initial_candidate_machine(self):
        '''初始化候选机器矩阵,产品号-工序号-候选机器矩阵[:candidate_machien_index[product_num][operation_num]]'''
        candidate_machine = np.zeros(shape=(
            self.product_total_num, self.product_operations.max(), self.machine_total_num), dtype=np.int64)
        for product_num in range(self.product_total_num):
            for operation_num in range(self.product_operations[product_num]):
                candidate_machine_o = np.where(
                    self.product_operation_detail[product_num][operation_num] != 0)[0]
                candidate_machine[product_num][operation_num][0:self.candidate_machine_index[product_num][operation_num]
                                                              ] = candidate_machine_o
        return candidate_machine

    def __initial_product_operation_detail(self):
        '''初始化加工详情矩阵,产品号-工序号-机器号-该工序下机器加工时间,int64[:,:,:],注 工序D为8小时,没有节的信息,工序C为节数*0.2小时'''
        product_operation_detail = np.zeros(
            shape=(self.product_total_num, self.product_operations.max(), self.machine_total_num), dtype=np.int64)
        for product_num in range(self.product_total_num):
            product_id = self.product_id[product_num]
            route_id = self.product_info[self.product_info['product_id']
                                         == product_id]['route_id'].values[0]
            route_id_df = self.route_info[self.route_info['route_id'] == route_id]
            for operation_num in range(self.product_operations[product_num]):
                equipment_type = route_id_df.iloc[operation_num]['equ_type']
                candidate_machine_df = self.equipment_info[self.equipment_info['equ_type']
                                                           == equipment_type]
                operation_time = route_id_df.iloc[operation_num]['time']
                operation_time = self.__get__real_time(operation_time)
                for machine_num in candidate_machine_df.index:
                    product_operation_detail[product_num][operation_num][machine_num] = operation_time
        return product_operation_detail

    def __initial_operations(self):
        '''初始化产品的工序数,产品号-该产品的工序数'''
        operations = np.empty(
            shape=(1, self.product_total_num), dtype=np.int64).flatten()
        for i in range(self.product_total_num):
            product_id = self.product_id[i]
            route_id = self.product_info[self.product_info['product_id']
                                         == product_id]['route_id'].values[0]
            route_id_df = self.route_info[self.route_info['route_id'] == route_id]
            operations[i] = route_id_df.shape[0]
        return operations

    def __initial_route_type(self):
        '''初始化机器码对应的工序类型,A->0,B->1,C->2,D->3'''
        route_type = np.empty(shape=(1, self.length), dtype=np.int64).flatten()
        position = 0
        for product_num in range(self.product_total_num):
            product_id = self.product_id[product_num]
            route_id = self.product_info[self.product_info['product_id']
                                         == product_id]['route_id'].values[0]
            route_id_df = self.route_info[self.route_info['route_id'] == route_id]
            for operation_num in range(self.product_operations[product_num]):
                route_name = route_id_df[route_id_df['route_No']
                                         == operation_num+1]['name'].values
                if route_name[0][2] == 'A':
                    route_type[position] = 0
                elif route_name[0][2] == 'B':
                    route_type[position] = 1
                elif route_name[0][2] == 'C':
                    route_type[position] = 2
                elif route_name[0][2] == 'D':
                    route_type[position] = 3
                else:
                    raise Exception
                position += 1
        return route_type

    def __get__real_time(self, operation_time):
        '''获取加工时间'''
        if operation_time == '0.2h':
            return 12
        if operation_time == '8h':
            return 480
        if operation_time == '4h':
            return 240
        else:
            return int(operation_time[0:3])

    def __initial_product_route(self):
        '''初始化产品工序类型,产品号-工序号-该工序的类型,int64[:,:]'''
        product_route = np.zeros(
            shape=(self.product_total_num, self.max_operation), dtype=np.int64)
        for product_num in range(self.product_total_num):
            product_id = self.product_id[product_num]
            route_id = self.product_info[self.product_info['product_id']
                                         == product_id]['route_id'].values[0]
            route_id_df = self.route_info[self.route_info['route_id'] == route_id]
            for operation_num in range(self.product_operations[product_num]):
                route_name = route_id_df[route_id_df['route_No']
                                         == operation_num+1]['name'].values
                if route_name[0][2] == 'A':
                    product_route[product_num][operation_num] = 0
                elif route_name[0][2] == 'B':
                    product_route[product_num][operation_num] = 1
                elif route_name[0][2] == 'C':
                    product_route[product_num][operation_num] = 2
                elif route_name[0][2] == 'D':
                    product_route[product_num][operation_num] = 3
        return product_route

    def __initial_C_time(self):
        C_time = 0
        for product_num in range(self.product_total_num):
            for operation_num in range(self.product_operations[product_num]):
                if self.product_route[product_num][operation_num] == 1:
                    C_time += 12*self.product_joints[product_num]
        return C_time

    def initial(self, size):
        '''
        自适应初始化
        '''
        self.best_MS = None
        self.best_OS = None
        self.best_score = float('inf')
        self.best_step = float('inf')
        self.size = size
        self.__initial_OS()

    def GA(self, max_step=20000, memory_size=0.2, tournament_M=3, max_no_new_best=50):
        decode_results = np.empty(
            shape=(1, self.size), dtype=np.int64).flatten()
        begin_time = np.empty(
            shape=(self.machine_total_num, self.product_total_num, self.max_operation), dtype=np.int64)
        end_time = np.empty(
            shape=(self.machine_total_num, self.product_total_num, self.max_operation), dtype=np.int64)
        begin_time_lists = np.empty(
            shape=(self.machine_total_num, self.product_operations.sum()), dtype=np.int64)
        end_time_lists = np.empty(
            shape=(self.machine_total_num, self.product_operations.sum()), dtype=np.int64)
        job_lists = np.empty(
            shape=(self.machine_total_num, self.product_operations.sum()), dtype=np.int64)
        operation_lists = np.empty(
            shape=(self.machine_total_num, self.product_operations.sum()), dtype=np.int64)
        product_operation = np.empty(
            shape=(1, self.product_total_num), dtype=np.int64).flatten()
        selected_machine = np.empty(
            shape=(self.product_total_num, self.max_operation), dtype=np.int64)
        machine_operations = np.empty(
            shape=(1, self.machine_total_num), dtype=np.int64).flatten()
        result = np.empty(shape=(1, 1), dtype=np.int64).flatten()
        memory_OS = np.empty(
            shape=(math.ceil(self.size*memory_size), self.length), dtype=np.int64)
        # 记忆库的解
        memory_results = np.empty(
            shape=(1, math.ceil(self.size*memory_size)), dtype=np.int64).flatten()
        memory_results.fill(np.int64(200000000))
        Crossover_P = np.empty(shape=(1, self.size),
                               dtype=np.float64).flatten()
        no_new_best = 0
        for step in range(max_step):
            if max_no_new_best <= no_new_best:
                break
            decode(self.OS,
                   decode_results, np.arange(
                       self.size, dtype=np.int64).flatten(),
                   self.machine_total_num,
                   self.product_operation_detail,
                   self.candidate_machine, self.candidate_machine_index,
                   begin_time, end_time,
                   begin_time_lists,  end_time_lists,
                   job_lists, operation_lists,
                   product_operation,
                   selected_machine,
                   machine_operations,
                   self.product_route, self.product_joints,
                   result)
            best_poeple = np.argmin(decode_results)
            if self.best_score > decode_results[best_poeple]:
                self.best_OS = self.OS[best_poeple]
                self.best_score = decode_results[best_poeple]
                self.best_step = step+1
                no_new_best = 0
                print(self.best_step)
                print(self.C_time/self.best_score)
                print()
            else:
                no_new_best += 1
            # if no_new_best > 20:
            #     self.OS_fill(self.best_OS)
            if memory_size > 0:
                update_memory_lib(memory_OS, memory_results,
                                  self.OS, decode_results)
            new_OS = np.empty(shape=self.OS.shape, dtype=np.int64)
            recodes = np.empty(shape=(1, self.size), dtype=int).flatten()
            tournament_memory(self.OS,
                              memory_OS,
                              new_OS,
                              tournament_M,
                              decode_results, memory_results,
                              step, max_step
                              )
            get_crossover_P(decode_results, self.best_score, Crossover_P)
            # Crossover_P.fill(1)
            jobs = np.array([range(self.product_total_num)],
                            dtype=np.int64).flatten()
            POX_crossover(new_OS, Crossover_P, jobs)
            random_mutation(new_OS, Crossover_P)
            self.OS = new_OS

    def __initial_OS(self):
        '''
        初始化OS
        '''
        self.OS = np.empty(shape=(self.size, self.length), dtype=np.int64)
        initial_OS(self.OS, self.size, self.product_operations,
                   self.product_route, self.length)
        # self.load_OS()
    
    def OS_fill(self,OS):
        for i in range(len(self.OS)):
            self.OS[i] = OS

    def load_OS(self):
        OS_ = np.load('save\\0.8812933025404157.npy')
        for i in range(len(self.OS)):
            self.OS[i] = OS_

    def __make_colors(self, numbers):
        colors = []
        COLOR_BITS = ['1', '2', '3', '4', '5', '6',
                      '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
        for i in range(numbers):
            colorBit = ['#']
            colorBit.extend(random.sample(COLOR_BITS, 6))
            colors.append(''.join(colorBit))
        return colors

    def __get_data(self, OS):
        # 初始化索引列表
        columns_index = ['machine_num', 'job_num',
                         'operation_num', 'begin_time', 'end_time']
        # 绘制甘特图的数据
        data = pd.DataFrame(data=None, columns=columns_index)
        # 解码 ### begin
        # 工件个数
        jobs_num = self.product_total_num
        # 机器数量
        machines_num = self.machine_total_num
        # 各工件的工序数
        jobs_operations = self.product_operations
        # 最大工序数
        max_operations = jobs_operations.max()
        # 记录最短时间的辅助列表
        result = np.empty(shape=(1, 1), dtype=np.int64).flatten()
        # 选用机器矩阵
        selected_machine = np.empty(
            shape=(jobs_num, max_operations), dtype=np.int64)
        # 开工时间矩阵
        begin_time = np.empty(
            shape=(machines_num, jobs_num, max_operations), dtype=np.int64)
        # 结束时间矩阵
        end_time = np.empty(
            shape=(machines_num, jobs_num, max_operations), dtype=np.int64)
        # 记录工件加工到哪道工序的矩阵
        product_operation = np.empty(
            shape=(1, jobs_num), dtype=np.int64).flatten()
        # 各机器开始时间矩阵
        begin_time_lists = np.empty(
            shape=(machines_num, jobs_operations.sum()), dtype=np.int64)
        # 各机器结束时间矩阵
        end_time_lists = np.empty(
            shape=(machines_num, jobs_operations.sum()), dtype=np.int64)
        # 记录各机器的工件列表
        job_lists = np.empty(
            shape=(machines_num, jobs_operations.sum()), dtype=np.int64)
        # 记录各机器的工序列表
        operation_lists = np.empty(
            shape=(machines_num, jobs_operations.sum()), dtype=np.int64)
        # 记录机器加工的步骤数
        machine_operations = np.empty(
            shape=(1, machines_num), dtype=np.int64).flatten()
        decode_one(OS,
                   self.machine_total_num,
                   self.product_operation_detail,
                   self.candidate_machine, self.candidate_machine_index,
                   begin_time, end_time,
                   begin_time_lists,  end_time_lists,
                   job_lists, operation_lists,
                   product_operation,
                   selected_machine,
                   machine_operations,
                   self.product_route, self.product_joints,
                   result)
        # 解码 ### end
        # 更新绘图数据 ### begin
        for machine_num in range(machines_num):
            begin_time_m = begin_time[machine_num]
            for job_num in range(jobs_num):
                begin_time_j = begin_time_m[job_num]
                for operation_num in range(jobs_operations[job_num]):
                    if begin_time_j[operation_num] >= 0:
                        new_data = []
                        new_data.append(machine_num)
                        new_data.append(job_num)
                        new_data.append(operation_num)
                        new_data.append(begin_time_j[operation_num])
                        new_data.append(
                            end_time[machine_num][job_num][operation_num])
                        new_data = pd.DataFrame(
                            [new_data], columns=columns_index)
                        data = pd.concat([data, new_data], copy=False)
        data['operation_time'] = data['end_time']-data['begin_time']
        return data

    def show_gantt_chart(self, figsize=(16, 4)):
        '''
        绘制甘特图
        '''
        OS = self.best_OS
        # 工件个数
        jobs_num = self.product_total_num
        # 获取数据
        data = self.__get_data(OS)
        # 生成颜色列表
        colors = self.__make_colors(jobs_num)
        # 更新绘图数据 ### end
        plt.figure(figsize=figsize)
        for index, row in data.iterrows():

            plt.barh(y=row['machine_num'],
                     width=row['operation_time'], left=row['begin_time'], color=colors[row['job_num']])
            plt.text(x=row['begin_time'], y=row['machine_num']-0.2, s='{}-{}'.format(
                row['job_num'], row['operation_num']), rotation=90)
        path = str(self.C_time/self.best_score)
        plt.savefig('save/'+path+'.png')
        plt.show()

    def save_result(self):
        data = self.__get_data(self.best_OS)
        print(self.C_time/self.best_score)
        if self.C_time/self.best_score < 0.8813:
            return
        path = str(self.C_time/self.best_score)
        columns_index = ['product_id', 'route_No',
                         'equ_name', 'start', 'duration', 'end']
        data['product_id'] = data.apply(
            lambda data: self.product_id[data['job_num']], axis=1)
        data['route_No'] = data.apply(
            lambda data: data['operation_num']+1, axis=1)
        data['equ_name'] = data.apply(
            lambda data: self.machine_id[data['machine_num']], axis=1)
        data['start'] = data.apply(
            lambda data: float(data['begin_time'])/60, axis=1)
        data['duration'] = data.apply(
            lambda data: float(data['operation_time'])/60, axis=1)
        data['end'] = data.apply(
            lambda data: float(data['begin_time']+data['operation_time'])/60, axis=1)
        self.check(data)
        data = data[columns_index]
        data.sort_values(by=['product_id', 'start'], inplace=True)
        data.to_csv('save/'+path+'.csv', index=False)
        np.save('save/'+path+'.npy', self.best_OS)

    def check(self, data):
        for machine_id in self.machine_id:
            machine_df = data[data['equ_name'] == machine_id]
            machine_df = machine_df.sort_values(by='start', inplace=False)
            start = 0
            end = 0
            for _, row in machine_df.iterrows():
                if row['start'] < end:
                    print('错误1')
                end = row['end']
        for product_id in self.product_id:
            product_df = data[data['product_id'] == product_id]
            product_df = product_df.sort_values(by='route_No', inplace=False)
            last_operation_time = 0
            end_time = 0
            route_NO = 0
            is_D = False
            D_count = 0
            for _, row in product_df.iterrows():
                if row['route_No'] != route_NO:
                    product_num = np.where(
                        self.product_id == row['product_id'])[0][0]
                    if is_D and D_count != self.product_joints[product_num]:
                        print('错误4')
                        print(product_id, row['equ_name'],
                              product_num, row['route_No']-1)
                        print(D_count, self.product_joints[product_num])
                    is_D = False
                    D_count = 1
                    last_operation_time = end_time
                    if row['start'] < last_operation_time:
                        print('错误2')
                    end_time = row['end']
                    route_NO = row['route_No']
                else:
                    is_D = True
                    if row['start'] < last_operation_time:
                        print('错误2')
                    end_time = max(row['end'], end_time)
                    D_count += 1
            is_B = False
            for _, row in product_df.iterrows():
                if is_B:
                    if row['start'] != end_time:
                        print('错误3')
                    is_B = False
                if row['equ_name'][0:2] == 'JM' or row['equ_name'][0:2] == 'JH':
                    end_time = row['end']
                    is_B = True
